﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NMF.Languages.Reflection
{
    public class ReflectiveTypeSystem : ITypeSystem
    {
        public ReflectiveTypeSystem()
        {
            RegisterKnownTypes();
        }

        protected virtual void RegisterKnownTypes()
        {
            RegisterKnownType(typeof(string), new StringType());
            RegisterKnownType(typeof(int), new IntegerType()
            {
                Minimum = int.MinValue,
                Maximum = int.MaxValue
            });
            //TODO: ...
        }

        protected void RegisterKnownType(System.Type originalType, IType type)
        {
            typeDict.Add(originalType, type);
        }

        private Dictionary<System.Type, IType> typeDict;

        public virtual IType Load(System.Type type)
        {
            IType result;
            if (!typeDict.TryGetValue(type, out result))
            {
                var rc = new ReflectiveClass(type, this);
                typeDict.Add(type, rc);
                foreach (var prop in type.GetProperties())
                {
                    rc.Properties.Add(LoadProperty(rc, prop));
                }
                result = rc;
            }
            return result;
        }

        protected virtual IProperty LoadProperty(ReflectiveClass reflectiveClass, System.Reflection.PropertyInfo property)
        {
            return new ReflectiveProperty(property, Load(property.PropertyType), reflectiveClass);
        }

        public IType GetType(string typeName)
        {
            System.Type type = System.Type.GetType(typeName);
            IType output;
            if (typeDict.TryGetValue(type, out output))
            {
                return output;
            }
            else
            {
                output = Load(type);
                return output;
            }
        }

        public IClass GetCommonBase(IClass class1, IClass class2)
        {
            var c1 = class1 as ReflectiveClass;
            var c2 = class2 as ReflectiveClass;

            if (c1 == null) return c2;
            if (c2 == null) return c1;

            if (c1.MappedType.IsAssignableFrom(c2.MappedType)) return c1;
            if (c2.MappedType.IsAssignableFrom(c1.MappedType)) return c2;

            throw new NotImplementedException();
        }

        public IEnumerable<IType> Types
        {
            get { return typeDict.Values; }
        }

        public void AssertDataType(DataType dataType)
        {
            if (dataType != null && dataType.SystemType != null)
            {
                if (!typeDict.ContainsKey(dataType.SystemType))
                {
                    throw new AssertionException();
                }
            }
            else
            {
                throw new AssertionException();
            }
        }
    }
}
